package com.huawei.openGauss;

import java.io.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.*;


public class WorkLoadManager {
    public static String delayLog;
    public static String tpsLog;
    public static String commitLog;
    public static int loopNum;
    public static int[] rates;
    public static int buffer;
    public static String URL;
    public static String username;
    public static String password;
    public static int numberCount = 0;
    public static String tableName;
    public static String tableSpace;
    public static int totalTrans;
    public static SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss.SSS");
    public static int threadCount;
    public static int tpsMax = 0;
    public static boolean autoCommit;
    public static boolean countFlag;
    public static String showContext;

    public WorkLoadManager() throws Exception {
        String configFile = "config.properties";
        InputStream in = new FileInputStream(configFile);
        Properties prop = new Properties();
        prop.load(in);

        delayLog = prop.getProperty("delayLog");
        tpsLog = prop.getProperty("tpsLog");
        commitLog = prop.getProperty("commitLog");
        loopNum = Integer.parseInt(prop.getProperty("loopNum"));
        buffer = Integer.parseInt(prop.getProperty("buffer"));
        URL = prop.getProperty("url");
        username = prop.getProperty("username");
        password = prop.getProperty("password");
        tableName = prop.getProperty("tableName");
        tableSpace = prop.getProperty("tableSpace");
        threadCount = Integer.parseInt(prop.getProperty("thread"));
        autoCommit = Boolean.parseBoolean(prop.getProperty("autoCommit"));
        countFlag = Boolean.parseBoolean(prop.getProperty("countFlag"));
        showContext = prop.getProperty("showContext");

        if (autoCommit && showContext.equals("commitDelay")) {
            System.out.println("Warning: When autoCommit is set to true, the commitDelay information only print select hit range!");
        }

        String ratesStr = prop.getProperty("rate");
        String[] ratesGrp = ratesStr.split(",");
        rates = new int[ratesGrp.length];
        for (int i = 0; i < rates.length; i++) {
            rates[i] = Integer.parseInt(ratesGrp[i]);
        }
    }

    public static synchronized void appendMethod(String fileName, String content) {
        try {
            FileWriter writer = new FileWriter(fileName, true);
            writer.write(content + "\r\n");
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void printAvgTps() throws IOException {
        int tpsTime = 0;
        int tpsTotal = 0;
        File file = new File(tpsLog);
        if (file.exists()) {
            InputStreamReader reader = new InputStreamReader(new FileInputStream(file));
            BufferedReader br = new BufferedReader(reader);
            String line = br.readLine();
            if (line.equals("timestamp,tps")) {
                line = br.readLine();
            }
            while (line != null) {
                tpsTime++;
                tpsTotal += Integer.parseInt(line.split(",")[1]);
                line = br.readLine();
            }
            reader.close();
            if (tpsTime == 0) {
                System.out.println("Please increase the data volume.");
            } else {
                if (showContext.equals("tps")) {
                    System.out.println("Total time: " +  tpsTime);
                    System.out.println("MAX tps:" + tpsMax);
                    System.out.println("Average tps: " + tpsTotal / tpsTime);
                }
            }
        }

    }

    private static void deleteFiles(String sPath) {
        File file = new File(sPath);
        if (file.exists() && file.isFile()) {
            //noinspection ResultOfMethodCallIgnored
            file.delete();
        }
    }

    public static void main(String[] args) {

        try {
            //noinspection InstantiationOfUtilityClass
            new WorkLoadManager();

            deleteFiles(delayLog);
            deleteFiles(tpsLog);
            deleteFiles(commitLog);

            WorkLoadManager.appendMethod(delayLog, "timestamp,insert(us),update(us),delete(us),query(us)");
            WorkLoadManager.appendMethod(tpsLog, "timestamp,tps");
            WorkLoadManager.appendMethod(commitLog, "timestamp,commit(ms)");

            KeySequence ks = new KeySequence();
            TimeMonitor tm = new TimeMonitor();
            ks.init();
            for (int i = 0; i < threadCount; i++) {
                new loadRunner(ks, tm, loopNum).start();
                //noinspection BusyWait
                Thread.sleep(100);
            }

            Thread.sleep(1000);

            while (true) {
                if (numberCount != 0) {
                    try {
                        int totalTrans_pre = totalTrans;
                        //noinspection BusyWait
                        Thread.sleep(1000);
                        int tps = totalTrans - totalTrans_pre;
                        if (tps > tpsMax) {
                            tpsMax = tps;
                        }
                        if (showContext.equals("tps")) {
                            System.out.println(df.format(new Date()) + "\t" + tps);
                        }
                        WorkLoadManager.appendMethod(tpsLog, df.format(new Date()) + "," + tps);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }

            printAvgTps();

            System.out.println("End");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Run WorkLoadManager failed.");
        }
    }

    public static class KeySequence {

        private int yearNum = 0;

        public void init() throws Exception {
            Class.forName("org.postgresql.Driver");
            Connection conn = DriverManager.getConnection(URL, username, password);
            System.out.println("init");
            Statement stmt = conn.createStatement();
            String sqlString;
            sqlString = String.format("select count(*) from %s", tableName);
            ResultSet rs = stmt.executeQuery(sqlString);
            rs.next();
            int countNum = rs.getInt(1);
            if (tableName.equals("kmyeb")) {
                yearNum = countNum / 1000;
            } else if (tableName.equals("mxz")) {
                yearNum = countNum / 10000;
            } else {
                System.out.println("Error table name.");
                throw new Exception();
            }
            rs.close();
            stmt.close();
            conn.close();
        }

        public String getRandomKmyebKey() {
            Random random = new Random();
            StringBuilder sb = new StringBuilder();
            String year = String.format("%04d", 1971 + random.nextInt(this.yearNum));
            String month = String.format("%02d", 1 + random.nextInt(10));
            String day = String.format("%02d", 1 + random.nextInt(10));
            String hour = String.format("%02d", 1 + random.nextInt(10));
            sb.append(year).append(month).append(day).append(hour);
            return sb.toString();
        }

        public String[] getRandomMxzKeys(int length) throws Exception {
            if (length < 0 || length > this.yearNum) {
                System.out.println("length should in (0, yearNum].");
                throw new Exception();
            }
            List<String> mxzKeys = new ArrayList<>(length);
            Random random = new Random();
            String mxzKey = "";
            for (int i = 0; i < length; i++) {
                while (mxzKey.isEmpty() || mxzKeys.contains(mxzKey)) {
                    mxzKey = String.format("%04d%02d0101", 1971 + random.nextInt(this.yearNum), 1 + random.nextInt(10));
                }
                mxzKeys.add(mxzKey);
            }
            return mxzKeys.toArray(new String[length]);
        }

    }

    @SuppressWarnings("unused")
    public static class TimeMonitor {

        private long totalInstTime;

        private long totalUptTime;

        private long totalDelTime;

        private long totalQueryTime;

        public long getTotalInstTime() {
            return totalInstTime;
        }

        public long getTotalUptTime() {
            return totalUptTime;
        }

        public long getTotalDelTime() {
            return totalDelTime;
        }

        public long getTotalQueryTime() {
            return totalQueryTime;
        }

        public long getCurrentTime() {
            return System.currentTimeMillis();
        }

        public long getCurrentTimeNano() {
            return System.nanoTime();
        }

        public synchronized void calTotalInstTime(long time) {
            totalInstTime = totalInstTime + time;
        }

        public synchronized void calTotalUptTime(long time) {
            totalUptTime = totalUptTime + time;
        }

        public synchronized void calTotalDelTime(long time) {
            totalDelTime = totalDelTime + time;
        }

        public synchronized void calTotalQueryTime(long time) {
            totalQueryTime = totalQueryTime + time;
        }
    }

    public static class loadRunner extends Thread {

        private Connection conn = null;
        private PreparedStatement stmt_sel = null;
        final private KeySequence ks;
        final private TimeMonitor tm;
        final private int loopNum;

        public loadRunner(KeySequence ks, TimeMonitor tm, int loopNum) {
            this.ks = ks;
            this.tm = tm;
            this.loopNum = loopNum;
        }

        public void run() {
            numberCount = numberCount + 1;
            int workLoadIndex = numberCount;
            try {
                System.out.printf("WorkerLoad %d Start.%n", workLoadIndex);
                try {
                    conn = DriverManager.getConnection(URL, username, password);
                } catch (Exception e) {
                    e.printStackTrace();
                    sleep(5000);
                }
                conn.setAutoCommit(autoCommit);
                if (countFlag) {
                    if (tableName.equals("kmyeb")) {
                        stmt_sel = conn.prepareStatement("select count(*) from kmyeb where kmbh = ?");
                    } else {
                        stmt_sel = conn.prepareStatement("select count(*) from kmyeb a, mxz b where a.kmbh = b.kmbh and a.kmbh in (?, ?)");
                    }
                } else {
                    if (tableName.equals("kmyeb")) {
                        stmt_sel = conn.prepareStatement("select nd, kmbh, kmmc, kmfx, jffse, zyaud_dataid, kmjc from kmyeb where kmbh = ?");
                    } else {
                        stmt_sel = conn.prepareStatement("select a.nd, a.kmbh, a.kmfx, a.jffse, a.zyaud_dataid, b.kmmc, b.jfje from kmyeb a, mxz b where a.kmbh = b.kmbh and a.kmbh in (?, ?) limit 10");
                    }
                }
                int i = 0;
                long totalQueryTime = 0;
                String context = "";
                while (i < loopNum || loopNum == 0) {
                    Thread.sleep(1);
                    long startTime;
                    long endTime;
                    long queryTime = 0;
                    double commitTime;
                    int j0 = rates[0];
                    int j1 = rates[1];
                    int j2 = rates[2];
                    int j3 = rates[3];
                    long selectValue = 0;
                    try {
                        while (j0 > 0 || j1 > 0 || j2 > 0 || j3 > 0) {
                            if (j3 > 0) {
                                if (tableName.equals("kmyeb")) {
                                    String keyString = ks.getRandomKmyebKey();
                                    stmt_sel.setString(1, keyString);
                                } else {
                                    String[] mxzKeys = ks.getRandomMxzKeys(2);
                                    stmt_sel.setString(1, mxzKeys[0]);
                                    stmt_sel.setString(2, mxzKeys[1]);
                                }
                                startTime = tm.getCurrentTimeNano();
                                ResultSet resultSet = stmt_sel.executeQuery();
                                endTime = tm.getCurrentTimeNano();
                                while (resultSet.next()) {
                                    selectValue = selectValue + 1;
                                }
                                totalTrans = totalTrans + 1;
                                queryTime = (endTime - startTime) / 1000;
                                totalQueryTime = totalQueryTime + queryTime;
                                j3 = j3 - 1;
                            }
                            if (context.equals("")) {
                                context = String.format("%d", queryTime);
                            } else {
                                context = String.format("%s\n%d", context, queryTime);
                            }
                            queryTime = -1;
                        }
                        if (i % buffer == 0 || i == loopNum - 1) {
                            WorkLoadManager.appendMethod(delayLog, context);
                            context = "";
                        }
                        if (!autoCommit) {
                            startTime = tm.getCurrentTimeNano();
                            conn.commit();
                            endTime = tm.getCurrentTimeNano();
                            commitTime = (double) (endTime - startTime) / 1000000;
                            WorkLoadManager.appendMethod(commitLog, df.format(new Date()) + String.format(",%.3f", commitTime));
                            if (showContext.equals("commitDelay")) {
                                System.out.println(df.format(new Date()) + String.format("\t %4.3fms\tWorkLoad%d\tselectNum %3d", commitTime, workLoadIndex, selectValue));
                            }
                        } else {
                            if (showContext.equals("commitDelay")) {
                                System.out.println(df.format(new Date()) + String.format("\tWorkLoad%d\tselectNum %3d", workLoadIndex, selectValue));
                            }
                        }
                    } catch (Exception e) {
                        if (conn != null) {
                            e.printStackTrace();
                            System.out.printf("WorkLoad%s: Some mistake is occurred in this loop.%n\n", workLoadIndex);
                            if (!autoCommit) {
                                conn.rollback();
                            }
                        }
                    } finally {
                        i = i + 1;
                    }
                }
                tm.calTotalQueryTime(totalQueryTime);
            } catch (Exception e) {
                System.out.println(e.getMessage());
                this.run();
            } finally {
                try {
                    stmt_sel.close();
                    conn.setAutoCommit(true);
                    conn.close();
                    System.out.printf("WorkerLoad %d end.%n", workLoadIndex);
                    numberCount = numberCount - 1;
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
